package pd.struct;

import pd.struct.statement.StatementObject;

/**
 * An n-ary, labeled, arbitrarily ordered tree that represents a script. Each node represents a single statement of the script.
 *
 * A ScriptTree represents a script as a tree. Each node represents a statement, with non-leaves representing flow and block statements, and leaves representing assignments, procedure calls, return statements, etc.
 *
 * @author Joshua Swank
 */
public class ScriptTree
{
    /**
     * The tree's root node. Can be null.
     */
    private ScriptNode root = null;

    /**
     * Initializes a new tree with a null root.
     */
    public ScriptTree()
    {
    }

    /**
     * Initializes a new tree with the specified root value.
     *
     * @param root the new tree's root value.
     */
    public ScriptTree( StatementObject root )
    {
        this.root = new ScriptNode( root );
    }

    /**
     * Initializes a new tree with the specified root node.
     *
     * @param root the new tree's root node.
     */
    protected ScriptTree( ScriptNode root )
    {
        if( root != null )
        {
            if( !root.hasParent() )
                this.root = root;
            else
                this.root = root.clone();
        }
    }

    /**
     * Returns the total number of nodes in the tree.
     *
     * @return the tree's size.
     */
    public int size()
    {
        if( root != null )
            return root.getSize();
        else
            return 0;
    }

    /**
     * Returns the subforest composed of nodes from indices start to end.
     *
     * @param start the first node to be added to the forest.
     * @param end the last node to be added to the forest.
     * @param traversal the method of traversal used to determine node positions.
     * @return the subforest formed by nodes from indices start to end or null if the range is invalid.
     */
    public OrderedScriptForest getSubforest( int start, int end, Traversal traversal )
    {
        if( start < end && start >= 1 && end <= size() )
        {
            OrderedScriptForest forest = new OrderedScriptForest();
            ScriptNode[] nodes = new ScriptNode[end - start + 1];

            ScriptTreeIterator iterator = createIterator( traversal );
            iterator.to( start-1 );

            for( int i = 0; i <= end - start; i++ )
                nodes[i] = iterator.nextNode( traversal );

            //TODO: Move this to Traversal enum, add functionality to preorder
            switch( traversal )
            {
                case POSTORDER:
                    for( int i = nodes.length-1; i >= 0; i-- )
                    {
                        forest.add( nodes[i].getSubtree() );
                        i -= nodes[i].getSize();
                    }
                break;
            }

            return forest;
        }
        else
            return null;
    }

    /**
     * Creates a ScriptTreeIterator for the tree with the default traversal method.
     *
     * @return a new iterator for the tree.
     */
    public ScriptTreeIterator createIterator()
    {
        return new ScriptTreeIterator( this );
    }

    /**
     * Creates a new ScriptTreeIterator for the tree using the specified traversal method.
     *
     * @param traversal the traversal method used by the new iterator.
     * @return a new iterator for the tree.
     */
    public ScriptTreeIterator createIterator( Traversal traversal )
    {
        return new ScriptTreeIterator( this, traversal );
    }

    /**
     * Returns a string with basic information about the tree.
     *
     * @return a string with the tree's size and root value.
     */
    public String toString()
    {
        StringBuilder description = new StringBuilder();

        description.append( "ScriptTree\nSize: " );
        description.append( size() );
        description.append( "\nRoot: " );
        description.append( root );

        return description.toString();
    }

    /**
     * Returns true if the tree has a root.
     *
     * @return true if the tree has a root, false otherwise.
     */
    public boolean hasRoot()
    {
        return ( root != null );
    }

    /**
     * Returns the value of the node at the specified position.
     *
     * @param index the position of the node.
     * @param method the traversal method used to find the node.
     * @return the value of the node at the specified position.
     */
    public StatementObject get( int index, Traversal method )
    {
        ScriptTreeIterator iterator = createIterator();

        return iterator.to( index, method );
    }

    /**
     * Returns the value of the root node.
     *
     * @return the value of the root node or null if it doesn't exist.
     */
    public StatementObject getRootValue()
    {
        if( root != null )
            return root.getValue();
        else
            return null;
    }

    /**
     * Returns the tree's root node.
     *
     * @return the root node.
     */
    protected ScriptNode getRoot()
    {
        return root;
    }

    /**
     * Sets the tree's root node.
     *
     * @param root the new root node.
     */
    protected void setRoot( ScriptNode root )
    {
        this.root = root;
    }
}
